/*
 * Copyright 2022-2023 the original author(https://github.com/wj596)
 * 
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * </p>
 */
package org.jsets.fastboot.flowable.service.impl;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.transaction.Transactional;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.FlowNode;
import org.flowable.bpmn.model.StartEvent;
import org.flowable.editor.language.json.converter.BpmnJsonConverter;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.repository.Model;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.Execution;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.engine.runtime.ProcessInstanceQuery;
import org.flowable.identitylink.api.IdentityLink;
import org.flowable.task.api.Task;
import org.jsets.fastboot.flowable.config.FlowableConfig;
import org.jsets.fastboot.flowable.model.dto.CompleteTaskParam;
import org.jsets.fastboot.flowable.model.dto.CompleteTaskResult;
import org.jsets.fastboot.flowable.model.dto.FlowTaskResult;
import org.jsets.fastboot.flowable.model.dto.InstanceQuery;
import org.jsets.fastboot.flowable.model.dto.ProcessNode;
import org.jsets.fastboot.flowable.model.dto.StartInstanceParam;
import org.jsets.fastboot.flowable.model.dto.StartInstanceResult;
import org.jsets.fastboot.flowable.model.entity.CompositeModel;
import org.jsets.fastboot.flowable.model.entity.FlowInstance;
import org.jsets.fastboot.flowable.model.entity.FlowTask;
import org.jsets.fastboot.flowable.service.ICompositeModelService;
import org.jsets.fastboot.flowable.service.IFlowTaskService;
import org.jsets.fastboot.flowable.service.IInstanceService;
import org.jsets.fastboot.flowable.service.ITaskTraceService;
import org.jsets.fastboot.security.IAuthenticatedUserProvider;
import org.jsets.fastboot.util.CollectionUtils;
import org.jsets.fastboot.util.JsonUtils;
import org.jsets.fastboot.util.SpringContextHolder;
import org.jsets.fastboot.util.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

import lombok.extern.slf4j.Slf4j;

/**
 * @author wangjie (https://github.com/wj596)
 * @date 2021.06.25 11:23
 */
@Slf4j
@Service
public class InstanceService implements IInstanceService {

	@Autowired
	private FlowEngineService flowEngine;
	@Autowired
	private ITaskTraceService traceService;
	//@Autowired
	//private IFormContentService contentService;
	@Autowired
	private IFlowTaskService flowTaskService;
	@Autowired
	private ICompositeModelService compositeModelService;

	@Override
	@Transactional
	public StartInstanceResult startInstance(StartInstanceParam param) {
		RepositoryService repositoryService = this.flowEngine.getRepositoryService();
		ProcessDefinition processDefinition = null;
		if (StringUtils.notEmpty(param.getProcessDeploymentId())) {
			processDefinition = repositoryService.createProcessDefinitionQuery()
					.deploymentId(param.getProcessDeploymentId()).singleResult();
		} else {
			if (StringUtils.notEmpty(param.getProcessDefinitionKey())) {
				processDefinition = repositoryService.createProcessDefinitionQuery()
						.processDefinitionKey(param.getProcessDefinitionKey()).latestVersion().singleResult();
			}
		}
		Objects.requireNonNull(processDefinition, "参数 processDefinitionId 或 processDefinitionKey 无效");

		boolean hasForm = false;
		if (StringUtils.notEmpty(param.getFormModelId()) && StringUtils.notEmpty(param.getFormContent())) {
			hasForm = true;
		}
		
		Map<String, Object> variables = Maps.newHashMap();
		this.flowEngine.getIdentityService().setAuthenticatedUserId(param.getStartUser());
		SpringContextHolder.tryBeanThen(IAuthenticatedUserProvider.class, p -> {
			variables.put(FlowableConfig.PROCESS_START_USER_KEY, p.getCurrentUsernameIfLogined());
		});
		if (StringUtils.notEmpty(param.getStartUser())) {
			variables.put(FlowableConfig.PROCESS_START_USER_KEY, param.getStartUser());
		}
		if (Objects.nonNull(param.getVariables())) {
			variables.putAll(param.getVariables());
		}
		if (hasForm) {
			Map<String, Object> formValues = JsonUtils.parseToMap(param.getFormContent());
			variables.putAll(formValues);
		}
		
		variables.put("_FLOWABLE_SKIP_EXPRESSION_ENABLED", true);
		variables.put(FlowableConfig.PROCESS_LAST_ASSIGNEE_KEY, "");
		
		RuntimeService runtimeService = this.flowEngine.getRuntimeService();
		ProcessInstance instance = null;
		if (StringUtils.notEmpty(param.getBusinessKey())) {
			instance = runtimeService.startProcessInstanceById(processDefinition.getId(), param.getBusinessKey(), variables);
		} else {
			instance = runtimeService.startProcessInstanceById(processDefinition.getId(), variables);
		}
		Objects.requireNonNull(instance, "流程发起失败");

//		if (hasForm) {
//			FormContent content = new FormContent();
//			content.setModelId(param.getFormModelId());
//			content.setFlowInstanceId(instance.getId());
//			content.setContent(param.getFormContent());
//			this.contentService.save(content);
//		}

		Optional<CompositeModel> compositeModel = this.compositeModelService.findByDeploymentId(processDefinition.getDeploymentId());
		StartInstanceResult result = new StartInstanceResult();
		List<Task> tasks = this.flowEngine.getTaskService().createTaskQuery().processInstanceId(instance.getId()).list();
		if (tasks.size() == 0) {
			throw new RuntimeException("流程实例中不存在活动的节点");
		}
		
		//自动完成
		if ((compositeModel.isPresent() && compositeModel.get().getFirstNodeAutoComplete()) || param.getFirstNodeAutoComplete()) {
			if (tasks.size() > 1) {
				throw new RuntimeException("无法确定流程实例的首节点");
			}
			
			String firstTaskId = tasks.get(0).getId();
//			if (hasForm) {
//				variables.putAll(this.contentService.formContentToMap(param.getFormContent()));
//			}
			this.flowTaskService.autoComplete(firstTaskId, "首节点自动完成", variables);
		}

		tasks = this.flowEngine.getTaskService().createTaskQuery().processInstanceId(instance.getId()).list();
		result.setInstanceId(instance.getId());
		List<FlowTaskResult> nexts = Lists.newArrayList();
		tasks.forEach(t -> {
			FlowTaskResult next = new FlowTaskResult();
			next.setId(t.getId());
			next.setName(t.getName());
			next.setAssignee(t.getAssignee());
			List<IdentityLink> links = this.flowEngine.getTaskService().getIdentityLinksForTask(t.getId());
			List<String> candidateUsers = Lists.newArrayList();
			List<String> gandidateGruops = Lists.newArrayList();
			links.forEach(link -> {
				candidateUsers.add(link.getUserId());
			});
			links.forEach(link -> {
				candidateUsers.add(link.getGroupId());
			});
			next.setCandidateUsers(candidateUsers);
			next.setCandidateGruops(gandidateGruops);
			nexts.add(next);
		});
		result.setNexts(nexts);
		return result;
	}

	@Override
	public Page<FlowInstance> selectProcessInstancePage(InstanceQuery criteria) {
		ProcessInstanceQuery query = this.flowEngine.getRuntimeService().createProcessInstanceQuery();
		if (StringUtils.notEmpty(criteria.getDefinitionKey())) {
			query.processDefinitionKey(criteria.getDefinitionKey());
		}
		if (StringUtils.notEmpty(criteria.getDefinitionName())) {
			query.processDefinitionName(criteria.getDefinitionName());
		}
		if (StringUtils.notEmpty(criteria.getBusinessKey())) {
			query.processInstanceBusinessKeyLike(criteria.getBusinessKey());
		}
		query.orderByStartTime().desc();

		Page<FlowInstance> page = new Page<FlowInstance>();
		page.setTotal(query.count());
		List<FlowInstance> list = Lists.newArrayList();
		List<ProcessInstance> processInstances = query.listPage(criteria.getOffset(), criteria.getSize());
		processInstances.forEach(t -> {
			list.add(this.toFlowInstance(t));
		});
		page.setRecords(list);
		return page;
	}

	@Override
	public FlowInstance getProcessInstance(String id) {
		ProcessInstance processInstance = this.flowEngine.getRuntimeService().createProcessInstanceQuery().processInstanceId(id).singleResult();
		return this.toFlowInstance(processInstance);
	}
	
	@Override
	public FlowInstance getProcessInstanceByBusinessKey(String businessKey) {
    	ProcessInstance processInstance =  this.flowEngine.getRuntimeService().createProcessInstanceQuery().processInstanceBusinessKey(businessKey).singleResult();
    	if(Objects.nonNull(processInstance)) {
    		return this.toFlowInstance(processInstance);
    	}
		return null;
	}
	
	/**
	 * 挂起流程
	 * 
	 * @param taskId 任务ID
	 */
	public boolean suspendProcessByTaskId(String taskId) {
		Task currentTask = this.flowEngine.getTaskService().createTaskQuery().taskId(taskId).singleResult();
		this.flowEngine.getRuntimeService().suspendProcessInstanceById(currentTask.getProcessInstanceId());
		return true;
	}
	
	/**
	 * 挂起流程
	 * 
	 * @param taskId 任务ID
	 */
	public boolean suspendProcess(String instanceId) {
		this.flowEngine.getRuntimeService().suspendProcessInstanceById(instanceId);
		return true;
	}

	/**
	 * 删除流程实例
	 * 
	 * @param processInstanceId 实例ID
	 */
	public boolean deleteProcessInstance(String processInstanceId, String reason) {
		this.flowEngine.getRuntimeService().deleteProcessInstance(processInstanceId, reason);
		return true;
	}
	
	public boolean changeActivityState(String processInstanceId, String currentNodeId, String toNodeId) {
		this.flowEngine.getRuntimeService().createChangeActivityStateBuilder()
        	//流程实例id
			.processInstanceId(processInstanceId)
			//流程节点的id 是定义审批流时的id 第一个参数当前节点 第二个参数想要跳转的节点
			.moveActivityIdTo(currentNodeId, toNodeId)
			.changeState();
		return true;
	}
	
	public boolean changeActivityToEnd(String processInstanceId) {
		RuntimeService runtimeService = this.flowEngine.getRuntimeService();
			
	    ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
	    if(Objects.isNull(processInstance)) {
	    	return false;
	    }
	    	
	    Optional<CompositeModel> compositeModel = this.compositeModelService.findByDeploymentId(processInstance.getDeploymentId());
	    if(!compositeModel.isPresent()) {
	    	return false;
	    }
	    	
	    int endNodeCount = 0;
	    String endNodeId = "";
	    for(ProcessNode pn : this.compositeModelService.iterateFlowElements(compositeModel.get().getActReModelId())) {
	    	if (FlowableConfig.ACTIVITY_TYPE_END_EVENT.equals(pn.getType())) {
	    		endNodeCount = endNodeCount+1;
	    		endNodeId = pn.getId();
	    	}
	    }
	    	
		if (endNodeCount > 1 || StringUtils.isBlank(endNodeId) ) {
			return false;
		}
	    	
		List<Execution> executions = runtimeService.createExecutionQuery().parentId(processInstanceId).list();
		List<String> executionIds = new ArrayList<String>();
		executions.forEach(execution -> executionIds.add(execution.getId()));
		runtimeService.createChangeActivityStateBuilder().moveExecutionsToSingleActivityId(executionIds, endNodeId).changeState();

		return true;
	}

	private FlowInstance toFlowInstance(ProcessInstance processInstance) {
		FlowInstance fi = new FlowInstance();
		fi.setId(processInstance.getId());
		fi.setDeploymentId(processInstance.getDeploymentId());
		fi.setProcessDefinitionName(processInstance.getProcessDefinitionName());
		fi.setProcessDefinitionKey(processInstance.getProcessDefinitionKey());
		fi.setProcessDefinitionVersion(processInstance.getProcessDefinitionVersion());
		fi.setBusinessKey(processInstance.getBusinessKey());
		fi.setStartUserId(processInstance.getStartUserId());
		fi.setStartTime(processInstance.getStartTime());
		List<Task> tasks = this.flowEngine.getTaskService().createTaskQuery().processInstanceId(processInstance.getId()).list();
		if(CollectionUtils.notEmpty(tasks)) {
			List<FlowTask> currentTasks = Lists.newArrayList();
			tasks.forEach(task -> {
				FlowTask tlowTask = new FlowTask();
				tlowTask.setId(task.getId());
				tlowTask.setName(task.getName());
				currentTasks.add(tlowTask);
			});
			fi.setCurrentTasks(currentTasks);
		}
		return fi;
	}
	
}